from typing import Dict, Optional, Union, List, TypedDict, Any, Tuple
from dataclasses import dataclass

from django.db.models.query import QuerySet
from django.utils.timezone import now

from product import models
import product.utils.order as order
import product.utils.work_with_stripe as work_with_stripe


class TaxRateType(TypedDict):
    """ Тип налоговой ставки """

    display_name: str
    percentage: Union[int, float]


@dataclass
class OrderOptions:
    """ Определяем параметры для заказа """

    product: Optional[models.Product] = None
    list_of_products: Optional[QuerySet] = None
    discount: Optional[int] = None
    tax_rates: Optional[List[order.TaxRateType]] = None


class CustomOrderCheckoutSession(TypedDict, total=False):
    """ Пользовательский заказ, определенный через Checkout Session """

    list_of_products: QuerySet
    list_of_tax_rates: Optional[Tuple[Tuple[str, int]]]
    discount: Optional[int]


class OrderUtils:
    """ Инструменты для обработки запросов связанных с заказом """

    def get_last_order(self) -> models.Order:
        """ Получаем последний, не оплаченный заказ """

        number_of_unpaid_orders = len(
            models.Order.objects.filter(paid_for=False)
        )
        if number_of_unpaid_orders == 0:
            order = models.Order.objects.create()
        elif number_of_unpaid_orders == 1:
            order = models.Order.objects.get(paid_for=False)
        else:
            message = 'Активных заказов больше одного'
            raise ValueError(message)
        return order


    def make_order_complete(
            self,
            order: models.Order,
            list_of_products: QuerySet) -> Any:
        """ Выполняем завершение заказа """

        [order.product_set.add(product) for product in list_of_products]

        order.total_price_of_products = sum([product['price'] \
         for product in list_of_products.values('price')])

        order.paid_for = True
        order.date_of_placing_order = now().isoformat()
        order.save()


    def check_availability_of_product_in_order(
            self,
            order: models.Order,
            product: models.Product) -> bool:
        """ Проверяем наличие продукта в заказе """

        return product in order.product_set.all()


    def add_product_to_order(
            self,
            order: models.Order,
            product: models.Product) -> Dict[str, str]:
        """ Добавляем продукт в заказ """

        if self.check_availability_of_product_in_order(order, product):
            response = {
                'status': 'failure',
                'message': 'Продукт уже добавлен в корзину'
            }
        else:
            product.order = order
            product.save()
            response = {
                'status': 'success',
                'message': 'Продукт добавлен в корзину'
            }

        return response


    def get_order_products(self, type_of_dict=True) -> QuerySet:
        """ Получаем продукты заказа """

        order = self.get_last_order()

        if type_of_dict:
            order_products = order.product_set.all().values('name', 'price')
        else:
            order_products = order.product_set.all()

        return order_products


    def get_order_amount(self) -> int:
        """ Получаем сумму заказа """

        order = self.get_last_order()
        list_of_prices = [int(product.price) for product in order.product_set.all()]
        order_amount = sum(list_of_prices)
        return order_amount


    def get_quantity_of_order_products(self) -> int:
        """ Получаем количество продуктов заказа """

        order = self.get_last_order()
        return len(order.product_set.all())


    def add_discount_to_order(self,
              order: models.Order,
              discount: int) -> None:
        """ Добавляем скидку заказу """

        discount_object, _ = models.Discount.objects.get_or_create(
            percent_off=discount
        )
        order.discount = discount_object
        order.save()


    def get_discount_for_paid_session(
            self,
            discount: Optional[int]) -> Union[List, List[Dict['str', 'str']]]:
        """ Получаем и устанавливаем скидку для оплачиваемой сессии """

        if not discount: return []

        self.add_discount_to_order(
            order=self.get_last_order(),
            discount=discount
        )
        coupon_id = work_with_stripe.DiscountApiStripe().get_or_create_coupon(discount)
        return [{
            'coupon': coupon_id,
        }]


    def add_tax_rates_to_order(
        self,
        order: models.Order,
        tax_rates: int) -> None:
        """ Устанавливаем налоговую ставку для заказа """

        for tax_rate in tax_rates:
            tax_rate_object, _ = models.TaxRate.objects.get_or_create(
                name=tax_rate['display_name'],
                precentage=tax_rate['percentage']
            )
            order.tax_rates.add(tax_rate_object)
            order.save()


    def set_tax_rate_for_order(
            self,
            tax_rates: Optional[List[TaxRateType]],
            content_of_line_items: List[Dict[str, Union[str, int]]]
            ) -> None:
        """ Устанавливаем налоговую ставку для заказа """

        if not tax_rates: return

        tax_rates_ids = []
        self.add_tax_rates_to_order(
            order=self.get_last_order(),
            tax_rates=tax_rates
        )

        for tax_rate in tax_rates:
            tax_rate_id = work_with_stripe.TaxRateApiStripe() \
            .get_or_create_tax_rate(
                display_name=tax_rate['display_name'],
                percentage=tax_rate['percentage']
            )
            tax_rates_ids.append(tax_rate_id)

        for product in content_of_line_items:
            product['tax_rates'] = tax_rates_ids


    def add_additional_options_to_order(
            self,
            content_of_line_items: List[Dict[str, Any]],
            discount: Optional[int] = None,
            tax_rates: Optional[List[order.TaxRateType]] = None) -> Dict[str, Any]:
        """ Добавляем дополнительные опции в заказ если это требуется """

        data = {}
        order.OrderUtils().set_tax_rate_for_order(
            tax_rates,
            content_of_line_items
        )

        discount_on_order = order.OrderUtils().get_discount_for_paid_session(
            discount
        )
        data['discount_on_order'] = discount_on_order
        return data


    def add_product_to_order_payment_session(
        self,
        product: Optional[models.Product] = None,
        list_of_products: Optional[QuerySet] = None) -> Any:
        """ Добавляем продукт в сессию оплаты заказа """

        def get_contents_of_line_items_for_products(
                list_of_products: QuerySet) -> List[Dict[str, str]]:
            """ Получаем line_items списка продуктов """

            list_of_prices_id = work_with_stripe.PriceApiStripe().get_price_id_list_from_list_of_products(
                list_of_products
            )
            return [{'price': price, 'quantity': 1} \
                    for price in list_of_prices_id]


        def get_content_of_line_items_for_product(
                product: models.Product) -> List[Dict[str, Union[str, int]]]:
            """ Получаем содержимое line_items для одного продукта """

            price_id = work_with_stripe.PriceApiStripe().create_price_by_product_object(
                product
            )
            content_of_line_items = [{
                'price': price_id,
                'quantity': 1,
            }]
            return content_of_line_items


        # ВЫШЕ ОПРЕДЕЛЕНИЕ ФУНКЦИЙ

        if list_of_products:
            return get_contents_of_line_items_for_products(
                list_of_products
            )
        else:
            return get_content_of_line_items_for_product(
                product
            )


    def pay_custom_order_checkout_session(
            self,
            custom_order: CustomOrderCheckoutSession
            ) -> Dict[str, str]:
        """ Оплачиваем пользовательский заказ с помощью Checkout Session """

        def get_list_of_tax_rates(
                custom_order: CustomOrderCheckoutSession
            ) -> List[TaxRateType]:
            """ Получаем список налоговых ставок """

            list_of_tax_rates = custom_order.get('list_of_tax_rates')
            if not list_of_tax_rates: return []

            return [TaxRateType(display_name=tax_rate[0], percentage=tax_rate[1]) \
                      for tax_rate in list_of_tax_rates]


        # ВЫШЕ ОПРЕДЕЛЕНИЕ ФУНКЦИЙ

        list_of_tax_rates = get_list_of_tax_rates(
            custom_order
        )
        order_options = order.OrderOptions(
            list_of_products=custom_order['list_of_products'],
            discount=custom_order.get('discount', 0),
            tax_rates=list_of_tax_rates
        )
        checkout_session_id = work_with_stripe.WorkWithApiStripe().create_session_to_pay_for_selected_products(
            order_options
        )
        return {'checkout_session_id': checkout_session_id}
